package com.nineclock.attendance.service.Impl;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.filter.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.common.utils.DistanceUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;

@Service
@Transactional
public class AttendPunchServiceImpl implements AttendPunchService {
    @Autowired
    private AttendPunchMapper attendPunchMapper;

    @Autowired
    private AttendGroupService attendGroupService;

    /**
     * 考勤: 移动端打卡
     * @param attendPunchDTO
     */
    @Override
    public void punch(AttendPunchDTO attendPunchDTO) {
        // 1.参数校验
        if(attendPunchDTO==null){
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }
        // 2.封装打卡信息
        // 2.1 设置基本信息，员工id、公司id、创建时间
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCreateTime(new Date());
        // 2.2 设置打卡时间（以服务端为准，否则用户可以在客户端修改手机时间打卡）
        Date now = new Date();
        attendPunch.setPunchTime(now);
        attendPunch.setPunchDateStr(DateTimeUtil.dateToStr(now,DateTimeUtil.TIME_FORMAT_2));

        // 2.3 设置上午、下午打卡（1-上午，2-下午）
        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(now)+1);
        // 2.4 查询员工所属的考勤组信息
        AttendGroupDTO groupByUserId = attendGroupService.getAttendGroupByUserId();
        if (groupByUserId==null){
            throw new NcException(ResponseEnum.USER_NOT_MATCH_ATTENDGROUP);
        }
        // 2.5 判断是否有效范围内打卡，非有效范围抛出异常
        Boolean punchAreaFlag = this.validatePunchArea(attendPunchDTO,groupByUserId);
        if (!punchAreaFlag){
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        attendPunch.setAreaValid(punchAreaFlag);

        // 2.6 判断是否是工作日打卡，非工作日抛出异常
        boolean workDayFlag = this.validateWorkDay(groupByUserId);
        if(!workDayFlag){
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }
        attendPunch.setDayType(AttendEnums.DAY_TYPE_WORKDAY.value());

        // 2.7 设置打卡类型（打卡类型 1正常；2迟到；3早退；4旷工）
        this.setPunchType(attendPunch, groupByUserId);

        // 2.8 设置是否为有效打卡（上午以最早一次为有效，下午以最晚一次为有效）
        this.validateEffective(attendPunch);

        // 2.9 打卡来源
        attendPunch.setPunchSource(AttendEnums.PUNCH_SOURCE_PUNCH.value());

        attendPunchMapper.insert(attendPunch);
    }

    /**
     * 判断打卡类型 : 1正常；2迟到；3早退；4旷工
     * @param attendPunch
     * @param attendGroupDTO
     */
    private void setPunchType(AttendPunch attendPunch, AttendGroupDTO attendGroupDTO) {
        //获取考勤组中的允许迟到早退分钟数
        Integer allowLateMinutes = attendGroupDTO.getAllowLateMinutes();

        //获取考勤组中的允许旷工分钟数
        Integer lateMinutes = attendGroupDTO.getLateMinutes();

        //获取实际打卡时间
        Date punchTime = attendPunch.getPunchTime();

        //获取实际打卡的时分秒
        String punchTimeStr = DateTimeUtil.dateToStr(punchTime, DateTimeUtil.TIME_FORMAT_1);

        //默认正常
        AttendEnums punchType = AttendEnums.PUNCH_TYPE_OK;

        //判定
        if (attendPunch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()) { //上午
            //获取考勤组的设置上班时间
            String startWorkTime = attendGroupDTO.getStartWorkTime();

            //判定上班打卡时间
            //返回1 , 说明 : punchTimeStr > startWorkTime + allowLateMinutes
            if (DateTimeUtil.compareTime(punchTimeStr, startWorkTime, allowLateMinutes) == 1) {
                punchType = AttendEnums.PUNCH_TYPE_LATE; // 迟到

                //返回1 , 说明 : punchTimeStr > startWorkTime + lateMinutes
                if (DateTimeUtil.compareTime(punchTimeStr, startWorkTime, lateMinutes) == 1) {
                    punchType = AttendEnums.PUNCH_TYPE_STAYALAY; // 旷工
                }
            }

            attendPunch.setMorningPunchType(punchType.value());

        } else { //下午
            String offWorkTime = attendGroupDTO.getOffWorkTime();

            //判定下班打卡时间
            //返回1 , 说明 : offWorkTime > punchTimeStr + allowLateMinutes
            if (DateTimeUtil.compareTime(offWorkTime, punchTimeStr, allowLateMinutes) == 1) {
                punchType = AttendEnums.PUNCH_TYPE_EARLY; // 早退

                //返回1 , 说明 : offWorkTime > punchTimeStr + lateMinutes
                if (DateTimeUtil.compareTime(offWorkTime, punchTimeStr, lateMinutes) == 1) {
                    punchType = AttendEnums.PUNCH_TYPE_STAYALAY;  // 旷工
                }
            }
            attendPunch.setAfternoonPunchType(punchType.value());
        }
    }

    /**
     * 判断上下午打卡是否有效打卡
     * @param attendPunch
     * @return
     */
    private void validateEffective(AttendPunch attendPunch) {

        //根据条件查询数据库中当前用户的今日上午/下午 打卡记录
        LambdaQueryWrapper<AttendPunch> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AttendPunch::getCompanyUserId, attendPunch.getCompanyUserId()) //打卡员工ID
                .eq(AttendPunch::getPunchDateStr, attendPunch.getPunchDateStr()) //打卡时间
                .eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork()) //上午，下午
                .eq(AttendPunch::getAreaValid, true) //打卡范围有效
                .eq(AttendPunch::getPunchSource, AttendEnums.PUNCH_SOURCE_PUNCH.value()); // 打卡来源


        Integer punchCount = attendPunchMapper.selectCount(queryWrapper);
        //如果未获取到, 说明没有打过卡, 此次打卡, 无论是上午还是下午都是有效的
        if (punchCount == 0) {
            attendPunch.setEffectiveValid(true);
        } else {
            //如果获取到记录, 说明已经打过卡
            //如果是上午, 第一条有效, 其余都是无效的
            if (attendPunch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()) {
                attendPunch.setEffectiveValid(false);

            } else { //如果是下午, 最后一条有效, 其余都是无效的
                attendPunch.setEffectiveValid(true);
                //更新之前的打卡记录为无效, 以最后一次打卡为准
                AttendPunch ap = new AttendPunch();
                ap.setEffectiveValid(false);
                attendPunchMapper.update(ap, queryWrapper);
            }
        }
    }

    /**
     * 校验工作日
     * @param attendGroupDTO
     * @return
     */
    private boolean validateWorkDay(AttendGroupDTO attendGroupDTO) {
        // 使用hutool的工具类，获取今天是周几
        int week = DateUtil.thisDayOfWeek();   //1: 表示周日, 2: 表示周一, 3:表示周二
        List<String> workdays = attendGroupDTO.getWorkdays(); // [1,1,1,1,1,0,0]

        // 获取在集合中的下标
        if (week == 1) {
            week = 6;
        } else {
            week = week - 2;
        }

        Integer value = Integer.valueOf(workdays.get(week)); // 获取workdays集合元素
        return value == 1;
    }

    /**
     * 校验打卡范围
     * @param attendPunchDTO
     * @param attendGroupDTO
     * @return
     */
    private boolean validatePunchArea(AttendPunchDTO attendPunchDTO, AttendGroupDTO attendGroupDTO) {
        //比较"打卡坐标" 和 "考勤组打卡坐标"的距离
        Integer meter = DistanceUtil.getDistanceMeter(attendPunchDTO.getLat().doubleValue(), attendPunchDTO.getLng().doubleValue(), attendGroupDTO.getLat().doubleValue(), attendGroupDTO.getLng().doubleValue());
        //判定是否小于等于考勤组中设置的范围
        return meter <= attendGroupDTO.getAddressRange();
    }



}
